# Copyright (c) 2011 Sergey K.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.


from functools import partial

from rs.error import Error
from rs.status import BAD_REQUEST
from rs.response import (Response, ResponseBuilder,
                         make_response, make_error_response)


__all__ = [
    'Context',
]


class context_property(property):

    def __init__(self, fget, *args, **kwargs):
        property.__init__(self, fget)
        self.args, self.kwargs = args, kwargs

    def __nonzero__(self):
        return False

    def __call__(self, *args, **kwargs):
        obj = type(self).__new__(self.__class__)
        obj.__init__(self.fget, *args, **kwargs)
        return obj

    def __get__(self, context, cls=None):
        if context is None:
            return self
        return self.fget(context, *self.args, **self.kwargs)
            

class Context(object):

    def __init__(self, application, request):
        """

        """
        self._app = application
        self._request = request
        self._resource = None
        self._params = None
        self._response = None

    @context_property
    def entity(self):
        entity = self._request.entity
        if self._resource and entity is not None and self._resource.consumer:
            media, consume = self._resource.consumer
            if consume:
                entity = consume(entity)
        return entity

    @context_property
    def request(self):
        return self._request

    @context_property
    def uri(self):
        return self._request.uri

    @context_property
    def alias(self, *aliases, **kwargs):
        if not self._resource:
            return 
        for dict in (self._params.path,
                     self._params.query,
                     self._params.form):
            for alias in aliases:
                if alias in dict:
                    return dict[alias]
        if 'default' not in kwargs:
            raise ValueError('None of {0} values found'.format(aliases))
        return kwargs['default']

    @context_property
    def response(self):
        return self._response

    def resolve_params(self, resource):
        argspec, argdefs = self._app.get_args(resource)
        for i, arg in enumerate(argspec.args):
            try:
                if i == 0 and resource.type:
                    yield arg, self._app.get_instance(resource)
                elif isinstance(argdefs.get(arg), context_property):
                    yield arg, argdefs[arg].__get__(self)
                elif arg in self._params.path:
                    yield arg, self._params.path[arg]
                elif arg in self._params.query:
                    yield arg, self._params.query[arg]
                elif arg in self._params.form:
                    yield arg, self._params.form[arg]
                elif arg in argdefs:
                    yield arg, argdefs[arg]
                else:
                    raise Error(BAD_REQUEST,
                                'Parameter {0} wasn\'t supplied'.format(arg))
            except ValueError as e:
                raise Error(BAD_REQUEST, str(e))

    def get_response(self):
        from rs.request import check_preconditions, dispatch
        def build_response(result, make, cache, producer):
            if isinstance(result, Response):
                return result
            else:
                builder = ResponseBuilder(result, make_response=make)
                if cache:
                    builder.set_tag(cache.tag)
                    builder.set_cache(cache.cache_control)
                return builder.build(producer)
        try:
            self._resource, self._params = dispatch(self._request, self._app)
            cache = self._app.get_cache(self._resource)
            setattr(self._request, 'check_preconditions',
                    partial(check_preconditions,
                            self._request,
                            tag=cache.tag))
            result = self._resource.target(
                **dict(self.resolve_params(self._resource))
            )
        except Error as response: pass
        else:
            response = build_response(result,
                                      make_response,
                                      cache,
                                      self._resource.producer)
        finally:
            if hasattr(self._request, 'check_preconditions'):
                delattr(self._request, 'check_preconditions')
        if self._app.has_error_handler(response.status):
            self._response = response
            try:
                handler = self._app.get_error_handler(response.status)
                result = handler.target(
                    **dict(self.resolve_params(handler))
                )
            except Error as response: pass
            else:
                response = build_response(result,
                                          make_error_response(response.status),
                                          None,
                                          handler.producer)
        return response